/**
 * $Author: francesco.benincasa $
 * $Date: 2007-02-20 16:51:30 +0100 (mar, 20 feb 2007) $
 * $Rev: 485 $
 */
package org.stars.database.loader.xml;

import java.io.File;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.database.ConnectionPool;
import org.stars.database.ConnectionPoolManager;
import org.stars.database.connectionpool.DBCPConnectionPool;
import org.stars.database.connectionpool.DisabledConnectionPool;
import org.stars.database.connectionpool.JNDIConnectionPool;
import org.stars.util.StringUtility;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * 
 * <p>
 * Carica gli attributi della sicurezza da un file xml. I vari parametri della
 * sicurezza sono conservati in un file xml posto in
 * <strong>&lt;context-path&gt;/WEB-INF/websecurity</strong>. Il file si chiama
 * <strong>websecurity.xml </strong>e gli attributi sono memorizzati sulla falsa
 * riga di come vengono memorizzati i parametri di un'applicazione web
 * all'interno di un file <strong>web.xml</strong>. Tutti i parametri trovati
 * vengono messi nella classe <strong>AppSettings</strong>. Nel caso in cui
 * esistano gi&agrave; degli attributi aventi gi&agrave; lo stesso nome, essi
 * vengono sovrascritti.
 * </p>
 * <p>
 * Un esempio di come si pu&ograve; presentare un file che questa classe
 * pu&ograve; analizzare:
 * </p>
 * <p class="Stile1">
 * &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
 * </p>
 * <p class="Stile1">
 * &lt;websecurity&gt;
 * </p>
 * <p class="Stile1">
 * ...
 * </p>
 * <p class="Stile1">
 * &lt;context-param&gt;<br>
 * &lt;param-name&gt;WebSecurity.Database.Driver&lt;/param-name&gt;<br>
 * &lt;param-value&gt;oracle.jdbc.driver.OracleDriver&lt;/param-value&gt;<br>
 * &lt;description&gt;driver di connessione&lt;/description&gt;<br>
 * &lt;/context-param&gt;
 * </p>
 * <p class="Stile1">
 * ...
 * </p>
 * <p class="Stile1">
 * &lt;/websecurity&gt;
 * </p>
 * <p>
 * Il tag <strong>param-name</strong> contiene il nome del parametro. Esso
 * verr&agrave; utilizzato per referenziare l'attributo all'interno di tuta
 * l'applicazione. Il tag <strong>param-value</strong> &egrave; il valore
 * dell'attributo.Il tag <strong>description</strong> contiene una descrizione
 * dell'attributo.
 * </p>
 * <p class="Stile1">
 * &nbsp;
 * </p>
 */
public class ConnectionsLoader extends DefaultHandler {

	protected static final int DEFAULT_MAX_ACTIVE_CONNECTION = 5;

	/**
	 * Logger
	 * 
	 * @return logger
	 */
	protected static Log getLog() {
		return LogFactory.getLog(ConnectionsLoader.class);
	}

	/**
	 * Costruttore della classe.
	 * 
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public ConnectionsLoader() throws ParserConfigurationException, SAXException {
		parser = SAXParserFactory.newInstance().newSAXParser();
	}

	/**
	 * Invocato quando si inizia a leggere file.
	 */
	@Override
	public void startDocument() {
		m_nStatus = TagType.TAG_UNKNOWN;

		m_sAppName = "";
		m_sAppPwd = "";
		m_sAppUrl = "";
		m_sAppUser = "";

		m_sAppJndi = "";
		m_sAppDriver = "";

		m_bTraceable = false;
	}

	/**
	 * Viene invocato quando si legge un tag.
	 * 
	 * @param uri
	 *            Non documentato.
	 * @param name
	 *            Nome del tag.
	 * @param qName
	 *            Non documentato.
	 * @param attributes
	 *            Non documentato.
	 * @throws org.xml.sax.SAXException
	 *             Eccezione.
	 */
	@Override
	public void startElement(String uri, String name, String qName, Attributes attributes) throws SAXException {
		// name mi contiene il nome del tag

		if ("connection-pool".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_CONNECTION_POOL;

			m_sAppName = attributes.getValue("name");

			// imposta a true di default.
			bEnabled = StringUtility.nvl(attributes.getValue("enabled"), "true").equalsIgnoreCase("true");

			// prende l'attributo default e verifica se questo cp è
			// impostato come cp di default
			if (StringUtility.nvl(attributes.getValue("default"), "false").equalsIgnoreCase("true")) {
				ConnectionPoolManager cpm = ConnectionPoolManager.getInstance();
				cpm.setDefaultConnectionName(m_sAppName.toUpperCase());
				// impostiamo la connessione di default;
			}

			m_bTraceable = "true".equalsIgnoreCase(StringUtility.nvl(attributes.getValue("trace"), "false")) ? true
					: false;

			m_sAppValidationQuery = "select 1 from dual";
			m_nAppMaxActiveConnection = DEFAULT_MAX_ACTIVE_CONNECTION;

			m_sAppDriver = "oracle.jdbc.driver.OracleDriver";

			// per default facciamo in modo che punti a connection pool di tipo
			// DBCP
			m_AppConnectionPoolType = ConnectionPoolType.TYPE_DBCP;
			String sTempType = StringUtility.nvl(attributes.getValue("type"), "dbcp");

			m_sAppUser = "";
			m_sAppPwd = "";
			m_sAppJndi = "";

			if (sTempType.equalsIgnoreCase("dbcp")) {
				m_AppConnectionPoolType = ConnectionPoolType.TYPE_DBCP;
			} else if (sTempType.equalsIgnoreCase("jndi")) {
				m_AppConnectionPoolType = ConnectionPoolType.TYPE_JNDI;
			} else if (sTempType.equalsIgnoreCase("mixed-jndi-dbcp")) {
				m_AppConnectionPoolType = ConnectionPoolType.TYPE_MIXED_JNDI_DBCP;
			} else if (sTempType.equalsIgnoreCase("mixed-dbcp-jndi")) {
				m_AppConnectionPoolType = ConnectionPoolType.TYPE_MIXED_DBCP_JNDI;
			}
		} else if ("user".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_USER;
		} else if ("url".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_URL;
		} else if ("pwd".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_PWD;
		} else if ("jndi".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_JNDI;
		} else if ("maxActive".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_MAX_ACTIVE;
		} else if ("validationQuery".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_VALIDATION_QUERY;
		} else if ("driver".equalsIgnoreCase(qName)) {
			m_nStatus = TagType.TAG_DRIVER;
		} else {
			m_nStatus = TagType.TAG_UNKNOWN;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
	 * java.lang.String, java.lang.String)
	 */
	@Override
	public void endElement(String uri, String name, String qName) throws SAXException {
		Log log = getLog();
		ConnectionPoolManager cpm = ConnectionPoolManager.getInstance();
		ConnectionPool cp = null;

		if ("connection-pool".equalsIgnoreCase(qName)) {
			m_sAppName = m_sAppName.toUpperCase();

			if (bEnabled) {
				switch (m_AppConnectionPoolType) {
				case TYPE_JNDI:
					cp = new JNDIConnectionPool(m_sAppName, m_sAppJndi);
					cpm.setConnectionPool(m_sAppName, cp);
					break;
				case TYPE_DBCP:
					cp = new DBCPConnectionPool(m_sAppName, m_sAppUrl, m_sAppUser, m_sAppPwd, m_sAppDriver,
							m_nAppMaxActiveConnection, m_sAppValidationQuery);
					cpm.setConnectionPool(m_sAppName, cp);
					break;
				case TYPE_MIXED_DBCP_JNDI:
					cp = new DBCPConnectionPool(m_sAppName, m_sAppUrl, m_sAppUser, m_sAppPwd, m_sAppDriver,
							m_nAppMaxActiveConnection, m_sAppValidationQuery);
					if (!ConnectionLoaderHelper.isValidConnection(cp)) {
						log.debug("Ricreo connection pool " + m_sAppName);
						cp = new JNDIConnectionPool(m_sAppName, m_sAppJndi);
					}

					cpm.setConnectionPool(m_sAppName, cp);
					break;
				case TYPE_MIXED_JNDI_DBCP:
					cp = new JNDIConnectionPool(m_sAppName, m_sAppJndi);
					if (!ConnectionLoaderHelper.isValidConnection(cp)) {
						log.debug("Ricreo connection pool " + m_sAppName);
						cp = new DBCPConnectionPool(m_sAppName, m_sAppUrl, m_sAppUser, m_sAppPwd, m_sAppDriver,
								m_nAppMaxActiveConnection, m_sAppValidationQuery);
					}

					cpm.setConnectionPool(m_sAppName, cp);
					break;
				default:
					log.error("Connection pool " + m_sAppName + " non identificato");
					break;
				}
				log.debug("Connection pool " + m_sAppName.toUpperCase() + " enabled=" + bEnabled + " driver="
						+ m_sAppDriver + " type=" + m_AppConnectionPoolType + " trace=" + m_bTraceable);
			} else {
				// impostiamo un connection pool di default
				cp = new DisabledConnectionPool();
				m_sAppName = m_sAppName.toUpperCase();
				cp.setName(m_sAppName);

				cpm.setConnectionPool(m_sAppName, cp);
				log.debug("Il connection pool " + m_sAppName + " e' disabilitato (" + cp.getClass() + ")");
			}

			if (cp != null) {
				cp.setTraceable(m_bTraceable);
			}
		} else {
		}
	}

	/**
	 * Invocato quando si analizza il contenuto di un tag.
	 * 
	 * @param ch
	 *            Non documentato.
	 * @param start
	 *            Non documentato.
	 * @param length
	 *            Non documentato.
	 */
	@Override
	public void characters(char ch[], int start, int length) {
		String sApp = new String(ch, start, length);
		sApp = sApp.trim();

		if (sApp.length() > 0) {
			switch (m_nStatus) {
			case TAG_URL:
				m_sAppUrl = sApp;
				m_nStatus = TagType.TAG_UNKNOWN;
				break;
			case TAG_USER:
				m_sAppUser = sApp;
				m_nStatus = TagType.TAG_UNKNOWN;
				break;
			case TAG_PWD:
				m_sAppPwd = sApp;
				m_nStatus = TagType.TAG_UNKNOWN;
				break;
			case TAG_JNDI:
				m_sAppJndi = sApp;
				m_nStatus = TagType.TAG_UNKNOWN;
				break;
			case TAG_DRIVER:
				m_sAppDriver = sApp;
				break;
			case TAG_MAX_ACTIVE:
				if (StringUtility.isInteger(sApp)) {
					m_nAppMaxActiveConnection = Integer.parseInt(sApp);
				}
				break;
			case TAG_VALIDATION_QUERY:
				m_sAppValidationQuery = sApp.trim();
				break;
			default:
				break;
			}
		}
	}

	/**
	 * Invocato quando si sta per finire il documento.
	 */
	@Override
	public void endDocument() {

	}
	
	/**
	 * Get logger
	 * @return
	 */
	protected static Log getLogger()
	{
		return LogFactory.getLog(ConnectionsLoader.class);
	}

	protected SAXParser parser;

	/**
	 * Metodo per caricare un file.
	 * 
	 * @param filename
	 *            nome del file con il suo percorso assoluto da caricare.
	 * @throws java.lang.Exception
	 *             Non documentato.
	 */
	public void load(String filename) throws Exception {
		Log log = getLogger();
		try {
			File file = new File(filename);

			m_nStatus = TagType.TAG_UNKNOWN;

			log.info("Database main configuration file " + file.getAbsoluteFile());
			parser.parse(file, this);

			log.debug("Database main configuration file loaded");

		} catch (Exception e) {
			// segnaliamo errore
			log.error("There are problems to load file " + e.getMessage());
			throw (e);
		}
	}

	/**
	 * indica se il connection pool è abilitato o meno. Se non è abilitato.. non
	 * creiamo alcun connection pool
	 */
	private boolean bEnabled;

	/**
	 * Stato del parser. Indica qual è l'ultimo tag che è stato letto nel file.
	 */
	private TagType m_nStatus;

	/**
	 * Nome del parametro.
	 */
	private String m_sAppName;

	private boolean m_bTraceable;

	/**
	 * tipo di connessione
	 */
	private ConnectionPoolType m_AppConnectionPoolType;

	/**
	 * Nome del parametro.
	 */
	private String m_sAppUrl;

	/**
	 * Nome del parametro.
	 */
	private String m_sAppUser;

	/**
	 * Nome del parametro.
	 */
	private String m_sAppPwd;

	/**
	 * Url jndi della connessione al database.
	 */
	private String m_sAppJndi;

	/**
	 * numero massimo di connessioni
	 */
	private Integer m_nAppMaxActiveConnection;

	/**
	 * query di test
	 */
	private String m_sAppValidationQuery;

	/**
	 * driver
	 */
	private String m_sAppDriver;

	/**
	 * Enumeration per la rappresentazione dei tipi di tag che compongono l'xml.
	 * 
	 * @author Francesco Benincasa (908099)
	 * 
	 */
	private enum TagType {
		/**
		 * tag connection-pool. Contiene la definizione del connection pool
		 */
		TAG_CONNECTION_POOL,
		/**
		 * tag url. Contiene l'url del database.
		 */
		TAG_URL,
		/**
		 * tag user. Contiene il nome dello user da usare per la connessione.
		 */
		TAG_USER,
		/**
		 * tag pwd. Contiene la password da usare per la connessione.
		 */
		TAG_PWD,
		/**
		 * tag jndi. Contiene il nome della risorsa jndi da usare per la
		 * connessione.
		 */
		TAG_JNDI,

		/**
		 * tag max active, indica il numero massimo di connessioni che possono
		 * essere aperte
		 */
		TAG_MAX_ACTIVE,

		/**
		 * tag driver, indica quale driver jdbc utilizzare
		 */
		TAG_DRIVER,

		/**
		 * tag validationQuery. E' la query da eseguire per testare che la
		 * connessione sia attiva.
		 */
		TAG_VALIDATION_QUERY,

		/**
		 * tag sconosciuto.
		 */
		TAG_UNKNOWN
	};

	/**
	 * Rappresenta i tipi di connection pool che possono essere gestiti dal
	 * loader. I tipi sono:
	 * <ul>
	 * <li>ORACLE: connection pool gestito dalla classe <i>OracleWrapper</i>,
	 * che al suo interno gestisce</li>
	 * </ul>
	 * 
	 * @author Francesco Benincasa (908099)
	 * 
	 */
	private enum ConnectionPoolType {
		/**
		 * connessione di tipo jndi
		 */
		TYPE_JNDI,

		/**
		 * Connessione gestita con il connection pool DBCP
		 */
		TYPE_DBCP,

		/**
		 * Cerca prima una connessione con DBCP ed in caso di errore va su JNDI
		 */
		TYPE_MIXED_DBCP_JNDI,

		/**
		 * Cerca prima una connessione su JNDI ed in caso di errore su DBCP
		 */
		TYPE_MIXED_JNDI_DBCP,

		/**
		 * 
		 */
		TYPE_UNKNOWN
	};

}